﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media.Imaging;
using System.Windows.Controls;
using Manas.DynamicDeepZoom.CodeCamp.ItemModels;
using Manas.DynamicDeepZoom.Core;
using System.Windows;
using Manas.DynamicDeepZoom.CodeCamp.ItemControls;
using System.Windows.Media;
using System.IO;
using System.Threading;
using System.Windows.Threading;

namespace Manas.DynamicDeepZoom.CodeCamp
{
    public interface ITileService
    {
        byte[] GetTile(int level, int x, int y);

        byte[] GetLevel(int level);
    }

    public class TileService : ITileService
    {
        #region Fields

        IModel model;
        
        BitmapSource[] sources;
        RawSource[] rawsources;

        bool[] validLevels;

        int tileHeight;
        int tileWidth;

        #endregion

        #region Constructors

        public TileService(IModel model, bool[] levels, int tileHeight, int tileWidth)
        {
            this.sources = new BitmapSource[levels.Length];
            this.rawsources = new RawSource[levels.Length];

            this.model = model;

            this.validLevels = levels;

            this.tileHeight = tileHeight;
            this.tileWidth = tileWidth;
        }

        #endregion

        #region ITileService Members

        public byte[] GetLevel(int level)
        {
            // Get the full bitmap image representing the level and return it
            return GetImageRawSource(level);
        }

        public byte[] GetTile(int level, int x, int y)
        {
            // Filter invalid levels
            if (level >= this.validLevels.Length || !this.validLevels[level]) 
                return null;

            // If tile size is less than level size, we need to crop it
            if (Math.Pow(2, level) > tileHeight && Math.Pow(2, level) > tileWidth)
            {
                // Get the full bitmap image representing the level
                BitmapSource source = GetImageSource(level);

                // Crop it and resize it to extract the tile
                byte[] result = ExtractTile(source, level, x, y);

                // Return it
                return result;
            }
            // Otherwise, we just return the full level
            else
            {
                return GetImageRawSource(level);
            }
        }

        #endregion

        #region Auxiliary Methods

        private byte[] GetImageRawSource(int level)
        {
            // Filter invalid levels
            if (level >= this.validLevels.Length || !this.validLevels[level])
                return null;

            // Populate the level if it does not exist
            if (rawsources[level] == null) 
                GetImageSource(level);

            return rawsources[level] == null
                ? null : rawsources[level].Data;
        }

        private BitmapSource GetImageSource(int level)
        {
            // Generate the bitmap source for the logic level
            if (sources[level] == null)
            {
                // Rendering of WPF controls must run on a separate STA thread
                Thread stathread = new Thread(obj =>
                {   
                    // Create the control
                    // TODO: Use XamlWriter to create it programatically
                    var ctrl = new LevelsControl();

                    // Set current requested level
                    ctrl.LevelDefiner.Level = level;

                    // Set model as context
                    ctrl.DataContext = new { Model = model };

                    // Determine full size of the control
                    var size = GetSize(level);

                    // Render the control on the specified size
                    RenderControl(ctrl, size);

                    // Draw it and store in sources cache
                    sources[level] = Draw(ctrl, size);
                    var data = BitmapToByteArray(sources[level]);
                    rawsources[level] = new RawSource(data);
                });

                // Run
                stathread.SetApartmentState(ApartmentState.STA);
                stathread.Start();
                stathread.Join();
            }

            return sources[level];
        }

        private Size GetSize(int level)
        {
            var side = Math.Pow(2, level);
            var size = new Size(side, side);
            return size;
        }

        private byte[] ExtractTile(BitmapSource source, int level, int x, int y)
        {
            CroppedBitmap cropped = new CroppedBitmap(source, 
                new Int32Rect(x * tileWidth, y * tileHeight, tileWidth, tileHeight));
            return BitmapToByteArray(cropped);
        }

        private byte[] BitmapToByteArray(BitmapSource source)
        {
            BitmapEncoder encoder = new PngBitmapEncoder();
            encoder.Frames.Add(BitmapFrame.Create(source));

            MemoryStream stream = new MemoryStream();
            encoder.Save(stream);

            return stream.ToArray();
        }

        private static void RenderControl(LevelsControl ctrl, Size size)
        {
            ctrl.Measure(size);
            ctrl.Arrange(new Rect(size));
            ctrl.UpdateLayout();
        }

        private BitmapSource Draw(LevelsControl ctrl, Size size)
        {
            var render = new RenderTargetBitmap((int)size.Width, (int)size.Height, 96, 96, PixelFormats.Pbgra32);
            render.Render(ctrl);

            return render;
        }

        private class RawSource
        {
            public RawSource(byte[] data)
            {
                this.Data = data;
            }

            public byte[] Data { get; set; }
        }

        #endregion

    }

}
